# 데이터베이스 메타데이터로 작업하기
SQLAlchemy Core와 ORM은 파이썬 객체를 데이터베이스의 테이블과 컬럼처럼 사용할 수 있게 하기 위해서 만들어졌습니다. 이러한 것들을 데이터베이스 메타데이터로 사용할 수 있습니다.
메타데이터는 데이터를 기술하는 데이터를 설명합니다. 여기서 메타데이터는 구성된 테이블, 열, 제약 조건 및 기타 객체 정보 등을 말합니다.
# 테이블 객체를 만들고 메타데이터에 담기
관계형 데이터베이스에서는 쿼리를 통해 테이블을 만들지만, SQLAlchemy에서는 Python 객체를 통해 테이블을 만들 수 있습니다.
SQLAlchemy 표현 언어를 시작할려면 사용할려는 데이터베이스 테이블을 Table
객체로 만들어줘야합니다.
>>> from sqlalchemy import MetaData
>>> metadata = MetaData() # 테이블들의 메타 정보를 담게될 객체입니다.
>>>
>>> from sqlalchemy import Table, Column, Integer, String
>>> user_table = Table(
... 'user_account', # 데이터베이스에 저장될 table 이름입니다.
... metadata,
... Column('id', Integer, primary_key=True), # 이 테이블에 들어갈 컬럼입니다.
... Column('name', String(30)),
... Column('fullname', String),
... )
Table
객체를 통해 데이터베이스 테이블을 만들 수 있습니다.Column
을 통해 테이블의 컬럼을 구현합니다.- 기본적으로
Column(컬럼 이름, 데이터 유형)
와 같이 정의합니다.
- 기본적으로
Table
인스턴스를 만들고나면 다음처럼 만들어진 컬럼 정보를 알 수 있습니다.
>>> user_table.c.name
Column('name', String(length=30), table=<user_account>)
>>> user_table.c.keys()
['id', 'name', 'fullname']
# 단순 제약 선언하기
우리는 위의 user 테이블을 만드는 코드에서 Column('id', Integer, primary_key=True)
구문을 보았습니다.
이는 id
컬럼을 기본키로 둔다고 선언하는 것입니다.
기본키는 암시적으로 PrimaryKeyConstraint
객체에 구조로 선언되어있습니다. 이는 다음처럼 확인할 수 있습니다.
>>> user_table.primary_key
PrimaryKeyConstraint(Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False))
기본키와 더불어 외래키도 다음처럼 선언할 수 있습니다.
>>> from sqlalchemy import ForeignKey
>>> address_table = Table(
... "address",
... metadata,
... Column('id', Integer, primary_key=True),
... Column('user_id', ForeignKey('user_account.id'), nullable=False), # ForeignKey 객체로 외래 키를 선언합니다.
... Column('email_address', String, nullable=False)
... )
ForeignKey('테이블 이름.외래 키')
형태로 외래 키 컬럼을 선언할 수 있습니다.- 이 때
Column
객체의 데이터타입을 생략할 수 있습니다. 데이터타입은 외래 키에 해당하는 컬럼을 찾아서 자동으로 추론됩니다.
- 이 때
- 따로 설명하지 않았지만,
nullable=False
파라미터와 값을 넘김으로써 컬럼에NOT NULL
제약 조건을 선언할 수 있습니다.
# 데이터베이스에 적용하기
지금까지 SQLAlchemy로 데이터베이스 테이블을 선언했습니다. 이제 이렇게 선언한 테이블이 실제 데이터베이스에 생성되도록 해봅시다.
다음처럼 metadata.create_all()
을 실행합니다.
>>> metadata.create_all(engine)
# 위 코드는 `metadata` 인스턴스에 기록된 모든 테이블들을 생성합니다.
# 결과적으로는 아래 쿼리를 실행하게 됩니다.
BEGIN (implicit)
PRAGMA main.table_...info("user_account")
...
PRAGMA main.table_...info("address")
...
CREATE TABLE user_account (
id INTEGER NOT NULL,
name VARCHAR(30),
fullname VARCHAR,
PRIMARY KEY (id)
)
...
CREATE TABLE address (
id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
email_address VARCHAR NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(user_id) REFERENCES user_account (id)
)
...
COMMIT
# ORM 방식으로 테이블 메타데이터 정의하기
위의 데이터베이스 구조를 만들고 제약조건을 똑같이 사용하지만, 이번에는 ORM 방식으로 진행해보겠습니다.
# 레지스트리 설정하기
먼저 다음처럼 registry
객체를 만듭니다.
>>> from sqlalchemy.orm import registry
>>> mapper_registry = registry()
registery
객체는 MetaData
객체를 포함하고 있습니다.
>>> mapper_registry.metadata
MetaData()
이제 다음을 실행합니다.
>>> Base = mapper_registry.generate_base()
위 과정을 다음처럼
declarative_base
로 한 번에 할 수 있습니다.>>> from sqlalchemy.orm import declarative_base >>> Base = declarative_base()
# ORM 객체 선언하기
Base
객체를 상속받는 하위 객체를 정의함으로써 ORM 방식으로 데이터베이스의 테이블을 선언할 수 있습니다.
>>> from sqlalchemy.orm import relationship
>>> class User(Base):
... __tablename__ = 'user_account' # 데이터베이스에서 사용할 테이블 이름입니다.
...
... id = Column(Integer, primary_key=True)
... name = Column(String(30))
... fullname = Column(String)
...
... addresses = relationship("Address", back_populates="user")
...
... def __repr__(self):
... return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"
>>> class Address(Base):
... __tablename__ = 'address'
...
... id = Column(Integer, primary_key=True)
... email_address = Column(String, nullable=False)
... user_id = Column(Integer, ForeignKey('user_account.id'))
...
... user = relationship("User", back_populates="addresses")
...
... def __repr__(self):
... return f"Address(id={self.id!r}, email_address={self.email_address!r})"
User
, Address
객체는 Table
객체를 포함합니다.
다음처럼 __table__
속성을 통해 확인할 수 있습니다.
>>> User.__table__
Table('user_account', MetaData(),
Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False),
Column('name', String(length=30), table=<user_account>),
Column('fullname', String(), table=<user_account>), schema=None)
# ORM 객체 생성하기
위에서 정의한 뒤, 다음처럼 ORM
객체를 생성할 수 있습니다.
>>> sandy = User(name="sandy", fullname="Sandy Cheeks")
>>> sandy
User(id=None, name='sandy', fullname='Sandy Cheeks')
# 데이터베이스에 적용하기
이제 다음처럼 ORM으로 선언한 테이블을 실제로 데이터베이스에 적용되도록 할 수 있습니다.
>>> mapper_registry.metadata.create_all(engine)
>>> Base.metadata.create_all(engine)
# 기존 데이터베이스의 테이블을 ORM 객체로 불러오기
위의 모든 방법들은 테이블을 직접 선언하지않고, 데이터베이스에 테이블을 가져오는 방법이 있습니다.
코드는 아래와 같습니다.
>>> some_table = Table("some_table", metadata, autoload_with=engine)
BEGIN (implicit)
PRAGMA main.table_...info("some_table")
[raw sql] ()
SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE name = ? AND type = 'table'
[raw sql] ('some_table',)
PRAGMA main.foreign_key_list("some_table")
...
PRAGMA main.index_list("some_table")
...
ROLLBACK
이제 다음과 같이 사용할 수 있습니다.
>>> some_table
Table('some_table', MetaData(),
Column('x', INTEGER(), table=<some_table>),
Column('y', INTEGER(), table=<some_table>),
schema=None)